前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

从 Redux 中间件实现原理切入,理解“面向切面编程”|原理篇

文章学习导航
面试总览

30 秒速记

  • 核心判断:状态管理的核心是把状态变化约束成可追踪的数据流,中间件在派发边界扩展异步、日志和副作用
  • 原理主线:围绕 「认识 Redux 中间件」、「Redux 中间件是如何与 Redux 」、「thunk 中间件到底做了什么?」 建立输入、状态变化与输出之间的因果关系
  • 文章范围:通过剖析 Redux 中间件的实现原理,深入讲解中间件如何扩展 Redux 的功能,支持异步 action、日志记录等场景,并结合“面向切面编程”思想,帮助读者理解前端状态管理的灵活性与可扩展性。
  • 边界与代价:集中式状态不是越多越好;可变性、订阅粒度和异步取消决定性能与可维护性
  • 工程落地:选型要从状态归属、更新频率、调试需求和团队约束出发,而不是只比较 API 数量

Redux 中间件本质上是在 dispatch 与 reducer 之间增加一层可组合的拦截逻辑,用来统一处理异步和其他副作用。 applyMiddleware 作为 store enhancer 接管原始 dispatch,把各中间件通过 compose 组合成调用链,每层都可以处理当前 action,再用 next(action) 交给下一层。以 redux-thunk 为例,遇到函数类型的 action 就执行它并传入 dispatch、getState,普通对象则直接放行。这个机制类似面向切面编程:核心状态流不用改动,扩展逻辑集中挂在派发边界,但中间件顺序会影响实际执行顺序。

这篇文章不要按 API 清单来背。先用上面的 Mind Map 建立全局结构,再通过交互 DEMO 观察正常路径和边界路径如何改变状态;阅读正文时重点核对每一步的输入、负责执行的参与者、产生的中间状态以及最终可观察结果。遇到版本敏感结论,要把“历史实现”“当前行为”和“工程兼容策略”分开说明;遇到性能或架构取舍,则用实际指标、失败现象和验证手段支撑判断。

版本校准: 本文若分析 ReactDOM.render、旧生命周期或栈调和,应把它视为理解架构演进的历史路径。React 19 已移除 ReactDOM.render,当前客户端入口使用 createRoot;并发渲染也必须区分可中断的渲染阶段与同步提交阶段。迁移前对照 React 19 官方升级指南。

# 认识 Redux 中间件

中间件相关的信息将作为 createStore 函数的一个 function 类型的入参被传入。这里我们简单复习一下 createStore 的调用规则,示例代码如下:

# 中间件的引入

// 引入 redux
import { createStore, applyMiddleware } from 'redux'
......
// 创建 store
const store = createStore(
    reducer,
    initial_state,
    applyMiddleware(middleware1, middleware2, ...)
);
@前端进阶之旅: 代码已经复制到剪贴板

可以看到,redux 对外暴露了 applyMiddleware 这个方法。applyMiddleware 接受任意个中间件作为入参,而它的返回值将会作为参数传入 createStore,这就是中间件的引入过程

# 中间件的工作模式

中间件的引入,会为 Redux 工作流带来什么样的改变呢?这里我们以 redux-thunk 为例,从经典的“异步 Action”场景切入,一起看看中间件是如何帮我们解决问题的。

redux-thunk——经典的异步 Action 解决方案

在针对 Redux 源码主流程的分析中,我们不难看出这样一个规律——Redux 源码中只有同步操作,也就是说当我们 dispatch action 时,state 会被立即更新。

那如果想要在 Redux 中引入异步数据流,该怎么办呢?Redux 官方给出的建议是使用中间件来增强 createStore。支持异步数据流的 Redux 中间件有很多,其中最适合用来快速上手的应该就是 redux-thunk了。

redux-thunk 的引入和普通中间件无异,可以参考以下示例:

// 引入 redux-thunk
import thunkMiddleware from 'redux-thunk'
import reducer from './reducers'
// 将中间件用 applyMiddleware 包装后传入
const store = createStore(reducer, applyMiddleware(thunkMiddleware))
@前端进阶之旅: 代码已经复制到剪贴板
// 这里处理的是没有设定初始状态的情况,也就是第一个参数和第二个参数都传 function 的情况
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  // 此时第二个参数会被认为是 enhancer(中间件)
  enhancer = preloadedState;
  preloadedState = undefined;
}
@前端进阶之旅: 代码已经复制到剪贴板

这段代码告诉我们,在只传入两个参数的情况下,createStore 会去检查第二个参数是否是 function 类型,若是,则认为第二个参数是“enhancer”。这里的“enhancer”是“增强器”的意思,而 applyMiddleware 包装过的中间件,正是“增强器”的一种。这也就解释了为什么上面 redux-thunk 的调用示例中,applyMiddleware 调用明明是作为 createStore 的第二个参数被传入的,却仍然能够被识别为中间件信息

redux-thunk 带来的改变非常好理解,它允许我们以函数的形式派发一个 action,像这样(解析在注释里):

// axios 是一个用于发起异步请求的库
import axios from 'axios' 
// 引入 createStore 和 applyMiddleware
import { createStore, applyMiddleware } from 'redux';
// 引入 redux-thunk
import thunk from 'redux-thunk';
// 引入 reducer
import reducer from './reducers';
// 创建一个有 thunk 中间件加持的 store 对象
const store = createStore(
  reducer,
  applyMiddleware(thunk)
);
// 用于发起付款请求,并处理请求结果。由于涉及资金,我们希望感知请求的发送和响应的返回
// 入参是付款相关的信息(包括用户账密、金额等)
// 注意 payMoney 的返回值仍然是一个函数
const payMoney = (payInfo) => (dispatch) => {
  // 付款前发出准备信号
  dispatch({ type: 'payStart' })
  fetch().then(res => { dispatch()})
  return axios.post('/api/payMoney', {
    payInfo
  })
  .then(function (response) {
    console.log(response);
    // 付款成功信号
    dispatch({ type: 'paySuccess' })
  })
  .catch(function (error) {
    console.log(error);
    // 付款失败信号
    dispatch({ type: 'payError' })
  });
}
// 支付信息,入参
const payInfo = {
  userName: xxx,
  password: xxx,
  count: xxx,
  ......
}
// dispatch 一个 action,注意这个 action 是一个函数
store.dispatch(payMoney(payInfo));
@前端进阶之旅: 代码已经复制到剪贴板

这里我尝试用 redux-thunk 模拟了一个付款请求的发起 → 响应过程。

这个过程单从表面上看,和普通 Redux 调用最大的不同就是 dispatch 的入参从 action 对象变成了一个函数。这就不由得让人对 thunk 中间件加持下的 Redux 工作流心生好奇——action 入参必须是一个对象

要想搞清楚这个问题,你除了需要理解 thunk 的执行逻辑,更重要的是要知道 Redux 中间件是如何工作的。

# Redux 中间件是如何与 Redux 主流程相结合的

Redux 中间件将会在 action 被分发之后、到达 reducer 之前执行,对应到工作流中,它的执行时机如下图所示:

若有多个中间件,那么 Redux 会结合它们被“安装”的先后顺序,依序调用这些中间件,这个过程如下图所示:

中间件的执行时机,允许它在状态真正发生变化之前,结合 action 的信息做一些它想做的事情。

那么中间件又是如何“绕过” dispatch 的校验逻辑的呢?其实,“绕过”dispatch 只是咱们主观上的一个使用感受。dispatch 并非被“绕过”了,而是被“改写”了,改写它的不是别人,正是 applyMiddleware

读到这里,对于 Redux 中间件的工作模式,你需要牢牢把握以下两点:

  • 中间件的执行时机,即 action 被分发之后、reducer 触发之前;
  • 中间件的执行前提,即 applyMiddleware 将会对 dispatch 函数进行改写,使得 dispatch 在触发 reducer 之前,会首先执行对 Redux 中间件的链式调用。

结合这两点,再来看 redux-thunk 的源码,一切就会豁然开朗了。

# thunk 中间件到底做了什么?

// createThunkMiddleware 用于创建 thunk
function createThunkMiddleware(extraArgument) {
  // 返回值是一个 thunk,它是一个函数
  return ({ dispatch, getState }) => (next) => (action) => {
    // thunk 若感知到 action 是一个函数,就会执行 action
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    // 若 action 不是一个函数,则不处理,直接放过
    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
@前端进阶之旅: 代码已经复制到剪贴板

redux-thunk 主要做的事情,就是在拦截到 action 以后,会去检查它是否是一个函数。若 action 是一个函数,那么 redux-thunk 就会执行它并且返回执行结果;若 action 不是一个函数,那么它就不是 redux-thunk 的处理目标,直接调用 next,告诉 Redux “我这边的工作做完了”,工作流就可以继续往下走了。

# Redux 中间件机制是如何实现的

Redux 中间件是通过调用 applyMiddleware 来引入的,因此我们先看看 applyMiddleware 的源码

← 揭秘 Redux 设计思想与工作原理如何打造高性能的 React 应用 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合